package net.minecraft.component.type;

import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.PropertyMap;
import com.mojang.serialization.Codec;
import com.mojang.serialization.codecs.RecordCodecBuilder;
import io.netty.buffer.ByteBuf;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import net.minecraft.block.entity.SkullBlockEntity;
import net.minecraft.network.codec.PacketCodec;
import net.minecraft.network.codec.PacketCodecs;
import net.minecraft.util.Util;
import net.minecraft.util.Uuids;
import net.minecraft.util.dynamic.Codecs;
import org.jetbrains.annotations.Nullable;

public record ProfileComponent(Optional<String> name, Optional<UUID> uuid, PropertyMap properties, GameProfile gameProfile) {
	private static final Codec<ProfileComponent> BASE_CODEC = RecordCodecBuilder.create(
		instance -> instance.group(
				Codecs.PLAYER_NAME.optionalFieldOf("name").forGetter(ProfileComponent::name),
				Uuids.INT_STREAM_CODEC.optionalFieldOf("id").forGetter(ProfileComponent::uuid),
				Codecs.GAME_PROFILE_PROPERTY_MAP.optionalFieldOf("properties", new PropertyMap()).forGetter(ProfileComponent::properties)
			)
			.apply(instance, ProfileComponent::new)
	);
	public static final Codec<ProfileComponent> CODEC = Codec.withAlternative(
		BASE_CODEC, Codecs.PLAYER_NAME, name -> new ProfileComponent(Optional.of(name), Optional.empty(), new PropertyMap())
	);
	public static final PacketCodec<ByteBuf, ProfileComponent> PACKET_CODEC = PacketCodec.tuple(
		PacketCodecs.string(16).collect(PacketCodecs::optional),
		ProfileComponent::name,
		Uuids.PACKET_CODEC.collect(PacketCodecs::optional),
		ProfileComponent::uuid,
		PacketCodecs.PROPERTY_MAP,
		ProfileComponent::properties,
		ProfileComponent::new
	);

	public ProfileComponent(Optional<String> name, Optional<UUID> uuid, PropertyMap properties) {
		this(name, uuid, properties, createProfile(uuid, name, properties));
	}

	public ProfileComponent(GameProfile gameProfile) {
		this(Optional.of(gameProfile.getName()), Optional.of(gameProfile.getId()), gameProfile.getProperties(), gameProfile);
	}

	@Nullable
	public ProfileComponent resolve() {
		if (this.isCompleted()) {
			return this;
		} else {
			Optional<GameProfile> optional;
			if (this.uuid.isPresent()) {
				optional = (Optional<GameProfile>)SkullBlockEntity.fetchProfileByUuid((UUID)this.uuid.get()).getNow(null);
			} else {
				optional = (Optional<GameProfile>)SkullBlockEntity.fetchProfileByName((String)this.name.orElseThrow()).getNow(null);
			}

			return optional != null ? this.resolve(optional) : null;
		}
	}

	public CompletableFuture<ProfileComponent> getFuture() {
		if (this.isCompleted()) {
			return CompletableFuture.completedFuture(this);
		} else {
			return this.uuid.isPresent()
				? SkullBlockEntity.fetchProfileByUuid((UUID)this.uuid.get()).thenApply(this::resolve)
				: SkullBlockEntity.fetchProfileByName((String)this.name.orElseThrow()).thenApply(this::resolve);
		}
	}

	private ProfileComponent resolve(Optional<GameProfile> profile) {
		return new ProfileComponent((GameProfile)profile.orElseGet(() -> createProfile(this.uuid, this.name)));
	}

	private static GameProfile createProfile(Optional<UUID> uuid, Optional<String> name) {
		return new GameProfile((UUID)uuid.orElse(Util.NIL_UUID), (String)name.orElse(""));
	}

	private static GameProfile createProfile(Optional<UUID> uuid, Optional<String> name, PropertyMap properties) {
		GameProfile gameProfile = createProfile(uuid, name);
		gameProfile.getProperties().putAll(properties);
		return gameProfile;
	}

	public boolean isCompleted() {
		return !this.properties.isEmpty() ? true : this.uuid.isPresent() == this.name.isPresent();
	}
}
